// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_proxy_client_socket.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/profiler/scoped_tracker.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "net/base/auth.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/proxy_delegate.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_stream_parser.h"
#include "net/http/proxy_connect_redirect_http_stream.h"
#include "net/log/net_log.h"
#include "net/socket/client_socket_handle.h"
#include "url/gurl.h"

namespace net {

HttpProxyClientSocket::HttpProxyClientSocket(
    ClientSocketHandle* transport_socket,
    const std::string& user_agent,
    const HostPortPair& endpoint,
    const HostPortPair& proxy_server,
    HttpAuthController* http_auth_controller,
    bool tunnel,
    bool using_spdy,
    NextProto protocol_negotiated,
    ProxyDelegate* proxy_delegate,
    bool is_https_proxy)
    : io_callback_(base::Bind(&HttpProxyClientSocket::OnIOComplete,
        base::Unretained(this)))
    , next_state_(STATE_NONE)
    , transport_(transport_socket)
    , endpoint_(endpoint)
    , auth_(http_auth_controller)
    , tunnel_(tunnel)
    , using_spdy_(using_spdy)
    , protocol_negotiated_(protocol_negotiated)
    , is_https_proxy_(is_https_proxy)
    , redirect_has_load_timing_info_(false)
    , proxy_server_(proxy_server)
    , proxy_delegate_(proxy_delegate)
    , net_log_(transport_socket->socket()->NetLog())
{
    // Synthesize the bits of a request that we actually use.
    request_.url = GURL("https://" + endpoint.ToString());
    request_.method = "CONNECT";
    if (!user_agent.empty())
        request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
            user_agent);
}

HttpProxyClientSocket::~HttpProxyClientSocket()
{
    Disconnect();
}

int HttpProxyClientSocket::RestartWithAuth(const CompletionCallback& callback)
{
    DCHECK_EQ(STATE_NONE, next_state_);
    DCHECK(user_callback_.is_null());

    int rv = PrepareForAuthRestart();
    if (rv != OK)
        return rv;

    rv = DoLoop(OK);
    if (rv == ERR_IO_PENDING) {
        if (!callback.is_null())
            user_callback_ = callback;
    }

    return rv;
}

const scoped_refptr<HttpAuthController>&
HttpProxyClientSocket::GetAuthController() const
{
    return auth_;
}

bool HttpProxyClientSocket::IsUsingSpdy() const
{
    return using_spdy_;
}

NextProto HttpProxyClientSocket::GetProtocolNegotiated() const
{
    return protocol_negotiated_;
}

const HttpResponseInfo* HttpProxyClientSocket::GetConnectResponseInfo() const
{
    return response_.headers.get() ? &response_ : NULL;
}

HttpStream* HttpProxyClientSocket::CreateConnectResponseStream()
{
    return new ProxyConnectRedirectHttpStream(
        redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL);
}

int HttpProxyClientSocket::Connect(const CompletionCallback& callback)
{
    DCHECK(transport_.get());
    DCHECK(transport_->socket());
    DCHECK(user_callback_.is_null());

    // TODO(rch): figure out the right way to set up a tunnel with SPDY.
    // This approach sends the complete HTTPS request to the proxy
    // which allows the proxy to see "private" data.  Instead, we should
    // create an SSL tunnel to the origin server using the CONNECT method
    // inside a single SPDY stream.
    if (using_spdy_ || !tunnel_)
        next_state_ = STATE_DONE;
    if (next_state_ == STATE_DONE)
        return OK;

    DCHECK_EQ(STATE_NONE, next_state_);
    next_state_ = STATE_GENERATE_AUTH_TOKEN;

    int rv = DoLoop(OK);
    if (rv == ERR_IO_PENDING)
        user_callback_ = callback;
    return rv;
}

void HttpProxyClientSocket::Disconnect()
{
    if (transport_.get())
        transport_->socket()->Disconnect();

    // Reset other states to make sure they aren't mistakenly used later.
    // These are the states initialized by Connect().
    next_state_ = STATE_NONE;
    user_callback_.Reset();
}

bool HttpProxyClientSocket::IsConnected() const
{
    return next_state_ == STATE_DONE && transport_->socket()->IsConnected();
}

bool HttpProxyClientSocket::IsConnectedAndIdle() const
{
    return next_state_ == STATE_DONE && transport_->socket()->IsConnectedAndIdle();
}

const BoundNetLog& HttpProxyClientSocket::NetLog() const
{
    return net_log_;
}

void HttpProxyClientSocket::SetSubresourceSpeculation()
{
    if (transport_.get() && transport_->socket()) {
        transport_->socket()->SetSubresourceSpeculation();
    } else {
        NOTREACHED();
    }
}

void HttpProxyClientSocket::SetOmniboxSpeculation()
{
    if (transport_.get() && transport_->socket()) {
        transport_->socket()->SetOmniboxSpeculation();
    } else {
        NOTREACHED();
    }
}

bool HttpProxyClientSocket::WasEverUsed() const
{
    if (transport_.get() && transport_->socket()) {
        return transport_->socket()->WasEverUsed();
    }
    NOTREACHED();
    return false;
}

bool HttpProxyClientSocket::WasNpnNegotiated() const
{
    if (transport_.get() && transport_->socket()) {
        return transport_->socket()->WasNpnNegotiated();
    }
    NOTREACHED();
    return false;
}

NextProto HttpProxyClientSocket::GetNegotiatedProtocol() const
{
    if (transport_.get() && transport_->socket()) {
        return transport_->socket()->GetNegotiatedProtocol();
    }
    NOTREACHED();
    return kProtoUnknown;
}

bool HttpProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info)
{
    if (transport_.get() && transport_->socket()) {
        return transport_->socket()->GetSSLInfo(ssl_info);
    }
    NOTREACHED();
    return false;
}

void HttpProxyClientSocket::GetConnectionAttempts(
    ConnectionAttempts* out) const
{
    out->clear();
}

int64_t HttpProxyClientSocket::GetTotalReceivedBytes() const
{
    return transport_->socket()->GetTotalReceivedBytes();
}

int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len,
    const CompletionCallback& callback)
{
    DCHECK(user_callback_.is_null());
    if (next_state_ != STATE_DONE) {
        // We're trying to read the body of the response but we're still trying
        // to establish an SSL tunnel through the proxy.  We can't read these
        // bytes when establishing a tunnel because they might be controlled by
        // an active network attacker.  We don't worry about this for HTTP
        // because an active network attacker can already control HTTP sessions.
        // We reach this case when the user cancels a 407 proxy auth prompt.
        // See http://crbug.com/8473.
        DCHECK_EQ(407, response_.headers->response_code());
        LogBlockedTunnelResponse();

        return ERR_TUNNEL_CONNECTION_FAILED;
    }

    return transport_->socket()->Read(buf, buf_len, callback);
}

int HttpProxyClientSocket::Write(IOBuffer* buf, int buf_len,
    const CompletionCallback& callback)
{
    DCHECK_EQ(STATE_DONE, next_state_);
    DCHECK(user_callback_.is_null());

    return transport_->socket()->Write(buf, buf_len, callback);
}

int HttpProxyClientSocket::SetReceiveBufferSize(int32_t size)
{
    return transport_->socket()->SetReceiveBufferSize(size);
}

int HttpProxyClientSocket::SetSendBufferSize(int32_t size)
{
    return transport_->socket()->SetSendBufferSize(size);
}

int HttpProxyClientSocket::GetPeerAddress(IPEndPoint* address) const
{
    return transport_->socket()->GetPeerAddress(address);
}

int HttpProxyClientSocket::GetLocalAddress(IPEndPoint* address) const
{
    return transport_->socket()->GetLocalAddress(address);
}

int HttpProxyClientSocket::PrepareForAuthRestart()
{
    if (!response_.headers.get())
        return ERR_CONNECTION_RESET;

    // If the connection can't be reused, just return ERR_CONNECTION_CLOSED.
    // The request should be retried at a higher layer.
    if (!response_.headers->IsKeepAlive() || !http_stream_parser_->CanFindEndOfResponse() || !transport_->socket()->IsConnected()) {
        transport_->socket()->Disconnect();
        return ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
    }

    // If the auth request had a body, need to drain it before reusing the socket.
    if (!http_stream_parser_->IsResponseBodyComplete()) {
        next_state_ = STATE_DRAIN_BODY;
        drain_buf_ = new IOBuffer(kDrainBodyBufferSize);
        return OK;
    }

    return DidDrainBodyForAuthRestart();
}

int HttpProxyClientSocket::DidDrainBodyForAuthRestart()
{
    // Can't reuse the socket if there's still unread data on it.
    if (!transport_->socket()->IsConnectedAndIdle())
        return ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;

    next_state_ = STATE_GENERATE_AUTH_TOKEN;
    transport_->set_reuse_type(ClientSocketHandle::REUSED_IDLE);

    // Reset the other member variables.
    drain_buf_ = nullptr;
    parser_buf_ = nullptr;
    http_stream_parser_.reset();
    request_line_.clear();
    request_headers_.Clear();
    response_ = HttpResponseInfo();
    return OK;
}

void HttpProxyClientSocket::LogBlockedTunnelResponse() const
{
    ProxyClientSocket::LogBlockedTunnelResponse(
        response_.headers->response_code(),
        is_https_proxy_);
}

void HttpProxyClientSocket::DoCallback(int result)
{
    DCHECK_NE(ERR_IO_PENDING, result);
    DCHECK(!user_callback_.is_null());

    // Since Run() may result in Read being called,
    // clear user_callback_ up front.
    CompletionCallback c = user_callback_;
    user_callback_.Reset();
    c.Run(result);
}

void HttpProxyClientSocket::OnIOComplete(int result)
{
    DCHECK_NE(STATE_NONE, next_state_);
    DCHECK_NE(STATE_DONE, next_state_);
    int rv = DoLoop(result);
    if (rv != ERR_IO_PENDING)
        DoCallback(rv);
}

int HttpProxyClientSocket::DoLoop(int last_io_result)
{
    DCHECK_NE(next_state_, STATE_NONE);
    DCHECK_NE(next_state_, STATE_DONE);
    int rv = last_io_result;
    do {
        State state = next_state_;
        next_state_ = STATE_NONE;
        switch (state) {
        case STATE_GENERATE_AUTH_TOKEN:
            DCHECK_EQ(OK, rv);
            rv = DoGenerateAuthToken();
            break;
        case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
            rv = DoGenerateAuthTokenComplete(rv);
            break;
        case STATE_SEND_REQUEST:
            DCHECK_EQ(OK, rv);
            net_log_.BeginEvent(
                NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
            rv = DoSendRequest();
            break;
        case STATE_SEND_REQUEST_COMPLETE:
            rv = DoSendRequestComplete(rv);
            net_log_.EndEventWithNetErrorCode(
                NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
            break;
        case STATE_READ_HEADERS:
            DCHECK_EQ(OK, rv);
            net_log_.BeginEvent(
                NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
            rv = DoReadHeaders();
            break;
        case STATE_READ_HEADERS_COMPLETE:
            rv = DoReadHeadersComplete(rv);
            net_log_.EndEventWithNetErrorCode(
                NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
            break;
        case STATE_DRAIN_BODY:
            DCHECK_EQ(OK, rv);
            rv = DoDrainBody();
            break;
        case STATE_DRAIN_BODY_COMPLETE:
            rv = DoDrainBodyComplete(rv);
            break;
        case STATE_DONE:
            break;
        default:
            NOTREACHED() << "bad state";
            rv = ERR_UNEXPECTED;
            break;
        }
    } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE && next_state_ != STATE_DONE);
    return rv;
}

int HttpProxyClientSocket::DoGenerateAuthToken()
{
    next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
    return auth_->MaybeGenerateAuthToken(&request_, io_callback_, net_log_);
}

int HttpProxyClientSocket::DoGenerateAuthTokenComplete(int result)
{
    DCHECK_NE(ERR_IO_PENDING, result);
    if (result == OK)
        next_state_ = STATE_SEND_REQUEST;
    return result;
}

int HttpProxyClientSocket::DoSendRequest()
{
    next_state_ = STATE_SEND_REQUEST_COMPLETE;

    // This is constructed lazily (instead of within our Start method), so that
    // we have proxy info available.
    if (request_line_.empty()) {
        DCHECK(request_headers_.IsEmpty());
        HttpRequestHeaders authorization_headers;
        if (auth_->HaveAuth())
            auth_->AddAuthorizationHeader(&authorization_headers);
        if (proxy_delegate_) {
            proxy_delegate_->OnBeforeTunnelRequest(proxy_server_,
                &authorization_headers);
        }
        std::string user_agent;
        if (!request_.extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
                &user_agent)) {
            user_agent.clear();
        }
        BuildTunnelRequest(endpoint_, authorization_headers, user_agent,
            &request_line_, &request_headers_);

        net_log_.AddEvent(
            NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
            base::Bind(&HttpRequestHeaders::NetLogCallback,
                base::Unretained(&request_headers_),
                &request_line_));
    }

    parser_buf_ = new GrowableIOBuffer();
    http_stream_parser_.reset(new HttpStreamParser(
        transport_.get(), &request_, parser_buf_.get(), net_log_));
    return http_stream_parser_->SendRequest(
        request_line_, request_headers_, &response_, io_callback_);
}

int HttpProxyClientSocket::DoSendRequestComplete(int result)
{
    if (result < 0)
        return result;

    next_state_ = STATE_READ_HEADERS;
    return OK;
}

int HttpProxyClientSocket::DoReadHeaders()
{
    next_state_ = STATE_READ_HEADERS_COMPLETE;
    return http_stream_parser_->ReadResponseHeaders(io_callback_);
}

int HttpProxyClientSocket::DoReadHeadersComplete(int result)
{
    if (result < 0)
        return result;

    // Require the "HTTP/1.x" status line for SSL CONNECT.
    if (response_.headers->GetHttpVersion() < HttpVersion(1, 0))
        return ERR_TUNNEL_CONNECTION_FAILED;

    net_log_.AddEvent(
        NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
        base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));

    if (proxy_delegate_) {
        proxy_delegate_->OnTunnelHeadersReceived(
            HostPortPair::FromURL(request_.url),
            proxy_server_,
            *response_.headers);
    }

    switch (response_.headers->response_code()) {
    case 200: // OK
        if (http_stream_parser_->IsMoreDataBuffered())
            // The proxy sent extraneous data after the headers.
            return ERR_TUNNEL_CONNECTION_FAILED;

        next_state_ = STATE_DONE;
        return OK;

        // We aren't able to CONNECT to the remote host through the proxy.  We
        // need to be very suspicious about the response because an active network
        // attacker can force us into this state by masquerading as the proxy.
        // The only safe thing to do here is to fail the connection because our
        // client is expecting an SSL protected response.
        // See http://crbug.com/7338.

    case 302: // Found / Moved Temporarily
        // Attempt to follow redirects from HTTPS proxies, but only if we can
        // sanitize the response.  This still allows a rogue HTTPS proxy to
        // redirect an HTTPS site load to a similar-looking site, but no longer
        // allows it to impersonate the site the user requested.
        if (!is_https_proxy_ || !SanitizeProxyRedirect(&response_)) {
            LogBlockedTunnelResponse();
            return ERR_TUNNEL_CONNECTION_FAILED;
        }

        redirect_has_load_timing_info_ = transport_->GetLoadTimingInfo(
            http_stream_parser_->IsConnectionReused(),
            &redirect_load_timing_info_);
        transport_.reset();
        http_stream_parser_.reset();
        return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;

    case 407: // Proxy Authentication Required
        // We need this status code to allow proxy authentication.  Our
        // authentication code is smart enough to avoid being tricked by an
        // active network attacker.
        // The next state is intentionally not set as it should be STATE_NONE;
        if (!SanitizeProxyAuth(&response_)) {
            LogBlockedTunnelResponse();
            return ERR_TUNNEL_CONNECTION_FAILED;
        }
        return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_);

    default:
        // Ignore response to avoid letting the proxy impersonate the target
        // server.  (See http://crbug.com/137891.)
        // We lose something by doing this.  We have seen proxy 403, 404, and
        // 501 response bodies that contain a useful error message.  For
        // example, Squid uses a 404 response to report the DNS error: "The
        // domain name does not exist."
        LogBlockedTunnelResponse();
        return ERR_TUNNEL_CONNECTION_FAILED;
    }
}

int HttpProxyClientSocket::DoDrainBody()
{
    DCHECK(drain_buf_.get());
    DCHECK(transport_->is_initialized());
    next_state_ = STATE_DRAIN_BODY_COMPLETE;
    return http_stream_parser_->ReadResponseBody(
        drain_buf_.get(), kDrainBodyBufferSize, io_callback_);
}

int HttpProxyClientSocket::DoDrainBodyComplete(int result)
{
    if (result < 0)
        return ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;

    if (!http_stream_parser_->IsResponseBodyComplete()) {
        // Keep draining.
        next_state_ = STATE_DRAIN_BODY;
        return OK;
    }

    return DidDrainBodyForAuthRestart();
}

//----------------------------------------------------------------

} // namespace net
